package jsr292.java.lang.invoke;

import java.util.Arrays;

import jsr292.weaver.Debug;
import jsr292.weaver.opt.OptimizerContext;
import jsr292.weaver.opt.OptimizerSnippets;
import jsr292.weaver.opt.OptimizerVarAllocator;


class MHPermuter extends MethodHandle {
    private final MethodHandle mh;
    private final int[] reorder;
    
    public MHPermuter(MethodHandle mh, MethodType newType, int[] reorder) {
        super(newType);
        this.mh = mh;
        this.reorder = reorder;
    }
    
    private static Object permute(int reorderIndex, Object o1, Object o2) {
        switch(reorderIndex) {
        case 0:
            return o1;
        case 1:
            return o2;
        }
        throw assertion();
    }
    
    private static Object permute(int reorderIndex, Object o1, Object o2, Object o3) {
        switch(reorderIndex) {
        case 0:
            return o1;
        case 1:
            return o2;
        case 2:
            return o3;
        }
        throw assertion();
    }
    
    private static Object permute(int reorderIndex, Object o1, Object o2, Object o3, Object o4) {
        switch(reorderIndex) {
        case 0:
            return o1;
        case 1:
            return o2;
        case 2:
            return o3;
        case 3:
            return o4;
        }
        throw assertion();
    }
    
    private static Object permute(int reorderIndex, Object o1, Object o2, Object o3, Object o4, Object o5) {
        switch(reorderIndex) {
        case 0:
            return o1;
        case 1:
            return o2;
        case 2:
            return o3;
        case 3:
            return o4;
        case 4:
            return o5;
        }
        throw assertion();
    }
    
    private static Object permute(int reorderIndex, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) {
        switch(reorderIndex) {
        case 0:
            return o1;
        case 1:
            return o2;
        case 2:
            return o3;
        case 3:
            return o4;
        case 4:
            return o5;
        case 5:
            return o6;
        }
        throw assertion();
    }
    
    private static Object permute(int reorderIndex, Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) {
        switch(reorderIndex) {
        case 0:
            return o1;
        case 1:
            return o2;
        case 2:
            return o3;
        case 3:
            return o4;
        case 4:
            return o5;
        case 5:
            return o6;
        case 6:
            return o7;
        }
        throw assertion();
    }
    
    private static Object[] permuteArray(int[] reorder, Object... values) {
	int length = reorder.length;
	Object[] array = new Object[length];
	for(int i=0; i<length; i++) {
	    array[i] = values[reorder[i]];
	}
	return array;
    }
    
    @Override
    public Object $invoke$(Object o1) throws Throwable {
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(o1);
        case 2:
            return mh.$invoke$(o1, o1);
        case 3:
            return mh.$invoke$(o1, o1, o1);
        case 4:
            return mh.$invoke$(o1, o1, o1, o1);
        case 5:
            return mh.$invoke$(o1, o1, o1, o1, o1);
        case 6:
            return mh.$invoke$(o1, o1, o1, o1, o1, o1);
        case 7:
            return mh.$invoke$(o1, o1, o1, o1, o1, o1, o1);
        }
        Object[] array = new Object[reorder.length];
        Arrays.fill(array, o1);
        return mh.$invoke$(array);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2) throws Throwable {
        int[] reorder = this.reorder;
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(permute(reorder[0], o1, o2));
        case 2:
            return mh.$invoke$(permute(reorder[0], o1, o2),
                               permute(reorder[1], o1, o2));
        case 3:
            return mh.$invoke$(permute(reorder[0], o1, o2),
                               permute(reorder[1], o1, o2),
                               permute(reorder[2], o1, o2));
        case 4:
            return mh.$invoke$(permute(reorder[0], o1, o2),
                               permute(reorder[1], o1, o2),
                               permute(reorder[2], o1, o2),
                               permute(reorder[3], o1, o2));
        case 5:
            return mh.$invoke$(permute(reorder[0], o1, o2),
                               permute(reorder[1], o1, o2),
                               permute(reorder[2], o1, o2),
                               permute(reorder[3], o1, o2),
                               permute(reorder[4], o1, o2));
        case 6:
            return mh.$invoke$(permute(reorder[0], o1, o2),
                               permute(reorder[1], o1, o2),
                               permute(reorder[2], o1, o2),
                               permute(reorder[3], o1, o2),
                               permute(reorder[4], o1, o2),
                               permute(reorder[5], o1, o2));
        case 7:
            return mh.$invoke$(permute(reorder[0], o1, o2),
                               permute(reorder[1], o1, o2),
                               permute(reorder[2], o1, o2),
                               permute(reorder[3], o1, o2),
                               permute(reorder[4], o1, o2),
                               permute(reorder[5], o1, o2),
                               permute(reorder[6], o1, o2));
        }
        
        return mh.$invoke$(permuteArray(reorder), o1, o2);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3) throws Throwable {
	int[] reorder = this.reorder;
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3));
        case 2:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3),
                               permute(reorder[1], o1, o2, o3));
        case 3:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3),
                               permute(reorder[1], o1, o2, o3),
                               permute(reorder[2], o1, o2, o3));
        case 4:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3),
                               permute(reorder[1], o1, o2, o3),
                               permute(reorder[2], o1, o2, o3),
                               permute(reorder[3], o1, o2, o3));
        case 5:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3),
                               permute(reorder[1], o1, o2, o3),
                               permute(reorder[2], o1, o2, o3),
                               permute(reorder[3], o1, o2, o3),
                               permute(reorder[4], o1, o2, o3));
        case 6:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3),
                               permute(reorder[1], o1, o2, o3),
                               permute(reorder[2], o1, o2, o3),
                               permute(reorder[3], o1, o2, o3),
                               permute(reorder[4], o1, o2, o3),
                               permute(reorder[5], o1, o2, o3));
        case 7:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3),
                               permute(reorder[1], o1, o2, o3),
                               permute(reorder[2], o1, o2, o3),
                               permute(reorder[3], o1, o2, o3),
                               permute(reorder[4], o1, o2, o3),
                               permute(reorder[5], o1, o2, o3),
                               permute(reorder[6], o1, o2, o3));
        }
        
        return mh.$invoke$(permuteArray(reorder), o1, o2, o3);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4) throws Throwable {
	int[] reorder = this.reorder;
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4));
        case 2:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4),
                               permute(reorder[1], o1, o2, o3, o4));
        case 3:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4),
                               permute(reorder[1], o1, o2, o3, o4),
                               permute(reorder[2], o1, o2, o3, o4));
        case 4:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4),
                               permute(reorder[1], o1, o2, o3, o4),
                               permute(reorder[2], o1, o2, o3, o4),
                               permute(reorder[3], o1, o2, o3, o4));
        case 5:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4),
                               permute(reorder[1], o1, o2, o3, o4),
                               permute(reorder[2], o1, o2, o3, o4),
                               permute(reorder[3], o1, o2, o3, o4),
                               permute(reorder[4], o1, o2, o3, o4));
        case 6:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4),
                               permute(reorder[1], o1, o2, o3, o4),
                               permute(reorder[2], o1, o2, o3, o4),
                               permute(reorder[3], o1, o2, o3, o4),
                               permute(reorder[4], o1, o2, o3, o4),
                               permute(reorder[5], o1, o2, o3, o4));
        case 7:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4),
                               permute(reorder[1], o1, o2, o3, o4),
                               permute(reorder[2], o1, o2, o3, o4),
                               permute(reorder[3], o1, o2, o3, o4),
                               permute(reorder[4], o1, o2, o3, o4),
                               permute(reorder[5], o1, o2, o3, o4),
                               permute(reorder[6], o1, o2, o3, o4));
        }
        
        return mh.$invoke$(permuteArray(reorder), o1, o2, o3, o4);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5) throws Throwable {
	int[] reorder = this.reorder;
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5));
        case 2:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5),
                               permute(reorder[1], o1, o2, o3, o4, o5));
        case 3:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5),
                               permute(reorder[1], o1, o2, o3, o4, o5),
                               permute(reorder[2], o1, o2, o3, o4, o5));
        case 4:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5),
                               permute(reorder[1], o1, o2, o3, o4, o5),
                               permute(reorder[2], o1, o2, o3, o4, o5),
                               permute(reorder[3], o1, o2, o3, o4, o5));
        case 5:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5),
                               permute(reorder[1], o1, o2, o3, o4, o5),
                               permute(reorder[2], o1, o2, o3, o4, o5),
                               permute(reorder[3], o1, o2, o3, o4, o5),
                               permute(reorder[4], o1, o2, o3, o4, o5));
        case 6:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5),
                               permute(reorder[1], o1, o2, o3, o4, o5),
                               permute(reorder[2], o1, o2, o3, o4, o5),
                               permute(reorder[3], o1, o2, o3, o4, o5),
                               permute(reorder[4], o1, o2, o3, o4, o5),
                               permute(reorder[5], o1, o2, o3, o4, o5));
        case 7:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5),
                               permute(reorder[1], o1, o2, o3, o4, o5),
                               permute(reorder[2], o1, o2, o3, o4, o5),
                               permute(reorder[3], o1, o2, o3, o4, o5),
                               permute(reorder[4], o1, o2, o3, o4, o5),
                               permute(reorder[5], o1, o2, o3, o4, o5),
                               permute(reorder[6], o1, o2, o3, o4, o5));
        }
        
        return mh.$invoke$(permuteArray(reorder), o1, o2, o3, o4, o5);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6) throws Throwable {
        int[] reorder = this.reorder;
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6));
        case 2:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6));
        case 3:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6));
        case 4:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6));
        case 5:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6),
                               permute(reorder[4], o1, o2, o3, o4, o5, o6));
        case 6:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6),
                               permute(reorder[4], o1, o2, o3, o4, o5, o6),
                               permute(reorder[5], o1, o2, o3, o4, o5, o6));
        case 7:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6),
                               permute(reorder[4], o1, o2, o3, o4, o5, o6),
                               permute(reorder[5], o1, o2, o3, o4, o5, o6),
                               permute(reorder[6], o1, o2, o3, o4, o5, o6));
        }
        
        return mh.$invoke$(permuteArray(reorder), o1, o2, o3, o4, o5, o6);
    }
    
    @Override
    public Object $invoke$(Object o1, Object o2, Object o3, Object o4, Object o5, Object o6, Object o7) throws Throwable {
        int[] reorder = this.reorder;
        switch(reorder.length) {
        case 0:
            return mh.$invoke$();
        case 1:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7));
        case 2:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6, o7));
        case 3:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6, o7));
        case 4:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6, o7));
        case 5:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[4], o1, o2, o3, o4, o5, o6, o7));
        case 6:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[4], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[5], o1, o2, o3, o4, o5, o6, o7));
        case 7:
            return mh.$invoke$(permute(reorder[0], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[1], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[2], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[3], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[4], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[5], o1, o2, o3, o4, o5, o6, o7),
                               permute(reorder[6], o1, o2, o3, o4, o5, o6, o7));
        }
        return mh.$invoke$(permuteArray(reorder), o1, o2, o3, o4, o5, o6, o7);
    }
    
    @Override
    public Object $invoke$(Object... rest) throws Throwable {
	return mh.$invoke$(permuteArray(reorder, rest));
    }
    
    @Override
    public boolean isWeavable(Class<?> classBeingRedefined) {
        return mh.isWeavable(classBeingRedefined);
    }
    @Override
    public void weave(OptimizerContext context, OptimizerVarAllocator allocator) {
        if (Debug.DEBUG) {
            Debug.checkAllocator(allocator, this);
        }
        
	OptimizerSnippets.permute(context, allocator, mh, reorder);
    }
}
